#include "PrecompiledHeader.h"
#include "PerspectiveCamera.h"

/*--------------------------------------------------------------------------*/

PerspectiveCamera::PerspectiveCamera(float farDist, float nearDist, float fov) :
	ICamera(farDist, nearDist),
	m_fieldOfView(fov),
	m_cameraQuat(glm::quat()),
	m_quatSource(NULL)
{
	this->SetViewport(glm::vec4(0.0f, 0.0f, g_Settings->GetResolutionf().x, g_Settings->GetResolutionf().y));
	this->Update();
}

/*--------------------------------------------------------------------------*/

PerspectiveCamera::~PerspectiveCamera()
{

}

/*--------------------------------------------------------------------------*/

const float& PerspectiveCamera::GetFieldOfView() const
{
	return this->m_fieldOfView;
}

/*--------------------------------------------------------------------------*/

void PerspectiveCamera::SetFieldOfView(const float& fov)
{
	this->m_fieldOfView = fov;

	SetOn(this->m_updateFlags, ECameraUpdateFlag::ProjectionMatrixChanged);
	SetOn(this->m_updateFlags, ECameraUpdateFlag::FrustumChanged);
}

/*--------------------------------------------------------------------------*/

bool PerspectiveCamera::UpdateFrustum()
{
	if (IsOn(this->m_updateFlags, ECameraUpdateFlag::FrustumChanged))
	{
		const float* vp = glm::value_ptr(this->m_viewProjectionMatrix);

		this->m_fL = glm::vec4(vp[3] + vp[0], vp[7] + vp[4], vp[11] + vp[8], vp[15] + vp[12]);
		this->m_fR = glm::vec4(vp[3] - vp[0], vp[7] - vp[4], vp[11] - vp[8], vp[15] - vp[12]);
		this->m_fB = glm::vec4(vp[3] + vp[1], vp[7] + vp[5], vp[11] + vp[9], vp[15] + vp[13]);
		this->m_fT = glm::vec4(vp[3] - vp[1], vp[7] - vp[5], vp[11] - vp[9], vp[15] - vp[13]);
		this->m_fN = glm::vec4(vp[3] + vp[2], vp[7] + vp[6], vp[11] + vp[10], vp[15] + vp[14]);
		this->m_fP = glm::vec4(vp[3] - vp[2], vp[7] - vp[6], vp[11] - vp[10], vp[15] - vp[14]);

		this->m_fL /= glm::fastLength(glm::vec3(this->m_fL.x, this->m_fL.y, this->m_fL.z));
		this->m_fR /= glm::fastLength(glm::vec3(this->m_fR.x, this->m_fR.y, this->m_fR.z));
		this->m_fB /= glm::fastLength(glm::vec3(this->m_fB.x, this->m_fB.y, this->m_fB.z));
		this->m_fT /= glm::fastLength(glm::vec3(this->m_fT.x, this->m_fT.y, this->m_fT.z));
		this->m_fN /= glm::fastLength(glm::vec3(this->m_fN.x, this->m_fN.y, this->m_fN.z));
		this->m_fP /= glm::fastLength(glm::vec3(this->m_fP.x, this->m_fP.y, this->m_fP.z));

		this->m_updateFlags &= ~ECameraUpdateFlag::FrustumChanged;

		return true;
	}

	return false;
}

/*--------------------------------------------------------------------------*/

bool PerspectiveCamera::UpdateProjectionMatrix()
{
	if (IsOn(this->m_updateFlags, ECameraUpdateFlag::ProjectionMatrixChanged) || IsOn(this->m_updateFlags, ECameraUpdateFlag::ViewportChanged))
	{
		float aspect = (this->m_viewport.z - this->m_viewport.x) / (this->m_viewport.w - this->m_viewport.y);

		this->m_projectionMatrix = glm::perspective(this->m_fieldOfView,
		                                            aspect,
		                                            this->m_nearDistance,
		                                            this->m_farDistance);

		this->m_invProjectionMatrix = glm::inverse(this->m_projectionMatrix);

		SetOff(this->m_updateFlags, ECameraUpdateFlag::ProjectionMatrixChanged);
		SetOff(this->m_updateFlags, ECameraUpdateFlag::ViewportChanged);

		return true;
	}

	return false;
}

/*--------------------------------------------------------------------------*/

bool PerspectiveCamera::UpdateViewMatrix()
{
	if (IsOn(this->m_updateFlags, ECameraUpdateFlag::ViewMatrixChanged))
	{
		this->m_viewMatrix = glm::toMat4(this->m_cameraQuat) * glm::translate(-this->m_position);
		this->m_invViewMatrix = glm::inverse(this->m_viewMatrix);

		this->m_strafedirection = glm::vec3(this->m_viewMatrix[0][0], this->m_viewMatrix[1][0], this->m_viewMatrix[2][0]);
		this->m_jumpdirection = glm::vec3(this->m_viewMatrix[0][1], this->m_viewMatrix[1][1], this->m_viewMatrix[2][1]);
		this->m_viewdirection = glm::vec3(this->m_viewMatrix[0][2], this->m_viewMatrix[1][2], this->m_viewMatrix[2][2]);

		this->m_SWdirection = -(this->m_strafedirection + this->m_jumpdirection);
		this->m_SEdirection = this->m_strafedirection - this->m_jumpdirection;
		this->m_NEdirection = this->m_strafedirection + this->m_jumpdirection;
		this->m_NWdirection = this->m_jumpdirection - this->m_strafedirection;

		SetOn(this->m_updateFlags, ECameraUpdateFlag::FrustumChanged);
		SetOff(this->m_updateFlags, ECameraUpdateFlag::ViewMatrixChanged);

		return true;
	}

	return false;
}

/*--------------------------------------------------------------------------*/

bool PerspectiveCamera::IsVisible(const glm::vec3& space, const float& radius) const
{
	glm::vec4 s = glm::vec4(space, 1.0);
	float mr = -radius;

	if (glm::dot(m_fL, s) <= mr
	    || glm::dot(m_fR, s) <= mr
	    || glm::dot(m_fB, s) <= mr
	    || glm::dot(m_fT, s) <= mr
	    || glm::dot(m_fN, s) <= mr
	    || glm::dot(m_fP, s) <= mr)
	{
		return false;
	}
	else
	{
		return true;
	}
}

/*--------------------------------------------------------------------------*/

void PerspectiveCamera::RotateX(float angInDeg)
{
	this->m_cameraQuat = this->m_cameraQuat * glm::angleAxis(angInDeg, this->m_strafedirection);

	SetOn(this->m_updateFlags, ECameraUpdateFlag::ViewMatrixChanged);
	SetOn(this->m_updateFlags, ECameraUpdateFlag::FrustumChanged);
}

/*--------------------------------------------------------------------------*/

void PerspectiveCamera::RotateY(float angInDeg)
{
	this->m_cameraQuat = this->m_cameraQuat * glm::angleAxis(angInDeg, this->m_jumpdirection);

	SetOn(this->m_updateFlags, ECameraUpdateFlag::ViewMatrixChanged);
	SetOn(this->m_updateFlags, ECameraUpdateFlag::FrustumChanged);
}

/*--------------------------------------------------------------------------*/

void PerspectiveCamera::RotateZ(float angInDeg)
{
	this->m_cameraQuat = this->m_cameraQuat * glm::angleAxis(angInDeg, this->m_viewdirection);;

	SetOn(this->m_updateFlags, ECameraUpdateFlag::ViewMatrixChanged);
	SetOn(this->m_updateFlags, ECameraUpdateFlag::FrustumChanged);
}

/*--------------------------------------------------------------------------*/

const glm::vec3& PerspectiveCamera::GetNEDirection() const
{
	return this->m_NEdirection;
}

/*--------------------------------------------------------------------------*/

const glm::vec3& PerspectiveCamera::GetNWDirection() const
{
	return this->m_NWdirection;
}

/*--------------------------------------------------------------------------*/

const glm::vec3& PerspectiveCamera::GetSEDirection() const
{
	return this->m_SEdirection;
}

/*--------------------------------------------------------------------------*/

const glm::vec3& PerspectiveCamera::GetSWDirection() const
{
	return this->m_SWdirection;
}

/*--------------------------------------------------------------------------*/

bool PerspectiveCamera::Update()
{
	if (this->m_quatSource)
	{
		if (this->m_quatSource->GetQuaterion() != this->m_cameraQuat)
		{
			this->SetQuaternion(this->m_quatSource->GetQuaterion());
		}
	}

	bool updatedSth = this->UpdateViewMatrix();
	updatedSth |= this->UpdateProjectionMatrix();

	if (updatedSth)
	{
		this->m_viewProjectionMatrix = this->m_projectionMatrix * this->m_viewMatrix;
		this->m_invViewProjectionMatrix = glm::inverse(this->m_viewProjectionMatrix);

		this->UpdateFrustum();

		return true;
	}

	return false;
}

/*--------------------------------------------------------------------------*/

glm::vec2 PerspectiveCamera::GetOnScreenCoords(const glm::mat4& modelmatrix, const glm::vec3& pnt, bool* visible) const
{
	glm::vec4 t2 = this->m_viewProjectionMatrix * modelmatrix * glm::vec4(pnt.x, pnt.y, pnt.z, 1.0f);
	t2.w = -t2.z;

	if (t2.w == 0.0f)
	{
		t2.x = t2.x + this->m_viewport.z / 2.0f;
		t2.y = this->m_viewport.w / 2.0f - t2.y;

		if (visible)
		{
			*visible = !(t2.x < 0.0f || t2.x > this->m_viewport.z || t2.y < 0.0f || t2.y > this->m_viewport.w);
		}

		return glm::vec2(t2.x, t2.y);
	}

	t2.w = 1.0f / t2.w;

	t2.x *= t2.w;
	t2.y *= t2.w;
	t2.z *= t2.w;

	t2.x = (-t2.x * 0.5f + 0.5f) * this->m_viewport.z;
	t2.y = (t2.y * 0.5f + 0.5f) * this->m_viewport.w;
	t2.z = (1.0f + t2.z) * 0.5f;

	if (visible)
	{
		*visible = (t2.z > 0.0f)  //tutaj zbedne to bo i tak sie wali
		           && !(t2.x < 0.0f || t2.x > this->m_viewport.z || t2.y < 0.0f || t2.y > this->m_viewport.w);
	}

	return glm::vec2(t2.x, g_Settings->GetResolutionf().y - t2.y);
}

/*--------------------------------------------------------------------------*/

void PerspectiveCamera::ZeroRotate()
{
	this->m_cameraQuat = glm::quat();

	SetOn(this->m_updateFlags, ECameraUpdateFlag::FrustumChanged);
	SetOn(this->m_updateFlags, ECameraUpdateFlag::ViewMatrixChanged);
}

/*--------------------------------------------------------------------------*/

void PerspectiveCamera::SetRotation(const glm::vec3& rotation)
{
	this->RotateX(rotation.x);
	this->RotateY(rotation.y);
	this->RotateZ(rotation.z);
}

/*--------------------------------------------------------------------------*/

const glm::quat& PerspectiveCamera::GetQuaterion() const
{
	return this->m_cameraQuat;
}

/*--------------------------------------------------------------------------*/

void PerspectiveCamera::SetQuaternion(const glm::quat& quat)
{
	this->m_cameraQuat = quat;

	SetOn(this->m_updateFlags, ECameraUpdateFlag::ViewMatrixChanged);
	SetOn(this->m_updateFlags, ECameraUpdateFlag::FrustumChanged);
}

/*--------------------------------------------------------------------------*/

void PerspectiveCamera::BindQuaternionSource(PerspectiveCamera* camera)
{
	this->m_quatSource = camera;
}

/*--------------------------------------------------------------------------*/

void PerspectiveCamera::UnbindQuaternionSource()
{
	this->m_quatSource = NULL;
}

/*--------------------------------------------------------------------------*/